package inutan.gateway.session.user;

import inutan.common.message.ActionErrorEnum;
import inutan.gateway.external.ExternalKit;
import inutan.gateway.external.GamePack;
import inutan.gateway.session.user.hook.UserHook;
import inutan.gateway.session.user.hook.UserHookDefault;
import luban.common.Command;
import org.ricks.common.lang.Logger;
import org.ricks.net.AioSession;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 用户 session 管理器
 * <pre>
 *     对所有用户UserSession的管理，统计在线用户等
 *
 *     关于用户管理 UserSessions 和 UserSession 可以参考这里：
 *     https://www.yuque.com/iohao/game/wg6lk7
 * </pre>
 */
public class UserSessions {

    /**
     * key : 玩家 id
     * value : UserSession
     */
    private final Map<Long, UserSession> userIdMap = new ConcurrentHashMap<>();

    /**
     * key : sessionId
     * value : UserSession
     */
    private final Map<Long, UserSession> userChannelIdMap = new ConcurrentHashMap<>();

    /**
     * key: roomId 1-1000000000000000
     * value: UserSession
     * 进入房间维护，退出房间解绑
     * UserSession close 销毁
     */
    private final Map<String,GroupUnit> userGroupIdMap = new ConcurrentHashMap<>();

    /** UserHook 上线时、下线时会触发 */
    private UserHook userHook = new UserHookDefault();


    /**
     * 获取 UserSession
     *
     * @param sessionId
     * @return UserSession
     */
    public UserSession getUserSessionBySessionId(long sessionId) throws RuntimeException {
        UserSession userSession = userChannelIdMap.get(sessionId);

        if (Objects.isNull(userSession)) {
            throw new RuntimeException("userSession 不存在，请先加入 session 管理中，UserSessions.add(sessionId)  curr sessionId = " + sessionId);
        }

        return userSession;
    }

    /**
     * true 用户存在
     *
     * @param userId 用户id
     * @return true 用户存在
     */
    public boolean existUserSession(long userId) {
        return this.userIdMap.containsKey(userId);
    }

    /**
     * true 用户存在
     *
     * @param sessionId 用户id
     * @return true 用户存在
     */
    public boolean existUserSessionBySessionId(long sessionId) {
        return this.userChannelIdMap.containsKey(sessionId);
    }

    /**
     * 获取 UserSession
     *
     * @param userId userId
     * @return UserSession
     */
    public UserSession getUserSessionByUserId(long userId) throws RuntimeException {
        UserSession userSession = this.userIdMap.get(userId);

        if (Objects.isNull(userSession)) {
            throw new RuntimeException("userSession 不存在，请先登录在使用此方法");
        }

        return userSession;
    }

    public UserSession getUserSession(long sessionId) throws RuntimeException {
        UserSession userSession = this.userChannelIdMap.get(sessionId);

        if (Objects.isNull(userSession)) {
            // 如果你登录了，但是又报这个异常，一般是将 ExternalGlobalConfig.verifyIdentity = false 了。
            throw new RuntimeException("userSession 不存在");
        }

        return userSession;
    }

    /**
     * 加入到 session 管理
     *
     * @param session
     */
    public void add(AioSession session) {
        try {
            logIp(session);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }


        UserSession userSession = new UserSession(session);

        Logger.info(" sessionId:"+session.sid()+" 正在加入session 管理容器");
        this.userChannelIdMap.putIfAbsent(session.sid(), userSession);
    }

    /**
     * 设置 channel 的 userId
     *
     * @param sessionId userChannelId
     * @param userId        userId
     * @return true 设置成功
     */
    public boolean settingUserId(long sessionId, long userId) {

        //顶号
        UserSession oldUserSession = userIdMap.get(userId);
        if(oldUserSession != null) {
            this.userIdMap.remove(userId);
            this.userChannelIdMap.remove(oldUserSession.getAioSession().sid());
            GamePack externalMessage = ExternalKit.createNetPack(Command.Login);
            // 强制玩家下线 状态码
            externalMessage.responseStatus = ActionErrorEnum.forcedOffline.getCode();

            oldUserSession.writeAndFlush(externalMessage);
        }

        UserSession userSession = this.getUserSession(sessionId);
        if (Objects.isNull(userSession)) {
            return false;
        }

        if (Boolean.FALSE.equals(userSession.isActiveChannel())) {
            removeUserSession(userSession);
            return false;
        }

        userSession.setUserId(userId);

        this.userIdMap.put(userId, userSession);
        userSession.setLoginSuccess(true);
        // 上线通知
        if (userSession.isVerifyIdentity()) {
            userHookInto(userSession);
        }

        return true;
    }


    /**
     * 移除 UserSession
     *
     * @param userSession userSession
     */
    public void removeUserSession(UserSession userSession) {

        if (userSession == null || userSession.getState() == UserSessionState.DEAD) {
            return;
        }

        long userId = userSession.getUserId();
        this.userIdMap.remove(userId);
        this.userChannelIdMap.remove(userSession.getAioSession().sid());

        if (userSession.getState() == UserSessionState.ACTIVE && userSession.isVerifyIdentity()) {
            userSession.setState(UserSessionState.DEAD);
            this.userHookQuit(userSession);
        }
        Logger.info("User Session close.......");
        // 关闭用户的连接
        userSession.close();
    }

    /**
     * 移除 UserSession
     *
     * @param  session
     */
    public void removeUserSession(AioSession session) {

        if (session == null) {
            return;
        }
        long sessionID = session.sid();
        UserSession userSession = this.userChannelIdMap.remove(sessionID);
        if(Objects.nonNull(userSession)) {
            this.userIdMap.remove(userSession.getUserId());
            remove(userSession);
            if (userSession.getState() == UserSessionState.ACTIVE) {
                userSession.setState(UserSessionState.DEAD);
                this.userHookQuit(userSession);
            }
        }
    }

    /**
     * 当前在线人数
     *
     * @return 当前在线人数
     */
    public long countOnline() {
        return this.userIdMap.size();
    }

    /**
     * 全员消息广播
     * 消息类型 ExternalMessage
     *
     * @param msg 消息
     */
    public void broadcast(GamePack msg) {
        userIdMap.values().forEach(userSession -> userSession.writeAndFlush(msg));
    }

    /**
     * 上线通知。注意：只有进行身份验证通过的，才会触发此方法
     *
     * @param userSession userSession
     */
    private void userHookInto(UserSession userSession) {
        if (Objects.isNull(userHook) || !userSession.isVerifyIdentity()) {
            return;
        }

        this.userHook.into(userSession);
    }

    /**
     * 离线通知。注意：只有进行身份验证通过的，才会触发此方法
     *
     * @param userSession userSession
     */
    private void userHookQuit(UserSession userSession) {
        if (Objects.isNull(userHook)) {
            return;
        }

        this.userHook.quit(userSession);
    }

    private void logIp(AioSession session) throws IOException {
            InetSocketAddress socketAddress = session.getRemoteAddress();
            String remoteAddress = socketAddress.getAddress().getHostAddress();
            int remotePort = socketAddress.getPort();

            socketAddress = session.getLocalAddress();
            String localAddress = socketAddress != null ? socketAddress.getAddress().getHostAddress() : "null";
            int localPort = socketAddress.getPort();

            Logger.debug("localAddress::"+localAddress+":"+localPort+", remoteAddress::"+remoteAddress+":" + remotePort);
    }


    private UserSessions() {

    }

    public static UserSessions me() {
        return Holder.ME;
    }

    /** 通过 JVM 的类加载机制, 保证只加载一次 (singleton) */
    private static class Holder {
        static final UserSessions ME = new UserSessions();
    }

    public void setUserHook(UserHook userHook) {
        this.userHook = userHook;
    }



    /**
     * 将AioSession加入群组group
     *
     * @param group
     * @param session
     */
    public final synchronized void join(String group, UserSession session) {
        GroupUnit groupUnit = userGroupIdMap.computeIfAbsent(group,key -> new GroupUnit());
        groupUnit.groupList.add(session);
    }

    public final synchronized void remove(String group, UserSession session) {
        GroupUnit groupUnit = userGroupIdMap.get(group);
        if (groupUnit == null) {
            return;
        }
        groupUnit.groupList.remove(session);
        if (groupUnit.groupList.isEmpty()) {
            userGroupIdMap.remove(group);
        }
    }

    public final void remove(UserSession session) {
        for (String group : userGroupIdMap.keySet()) {
            remove(group, session);
        }
    }

    public void writeToGroup(String group, GamePack gamePack) {
        GroupUnit groupUnit = userGroupIdMap.get(group);
        for (UserSession session : groupUnit.groupList) {
            session.writeAndFlush(gamePack);
        }
    }

    private class GroupUnit {
        Set<UserSession> groupList = new HashSet<>();
    }
}
